package com.atguigu.gmall.search.service.impl;

import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.search.dao.GoodsEsRepository;
import com.atguigu.gmall.search.service.SkuEsService;

import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.util.StringBuilders;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.Nested;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.HighlightQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.xml.ws.Response;
import java.util.Date;
import java.util.List;
import java.util.Optional;

/**
 * @author dyqstart
 * @create 2021-08-13-23:26
 * @since 1.0.0
 */
@Service
public class SkuEsServiceImpl implements SkuEsService {

    @Autowired
    GoodsEsRepository goodsEsRepository;

    @Autowired
    ElasticsearchRestTemplate restTemplate;



    @Override
    public void save(Goods goods) {
        goods.setCreateTime(new Date());//更新处理日期
        goodsEsRepository.save(goods);
    }

    @Override
    public void deleteById(Long skuId) {
        goodsEsRepository.deleteById(skuId);
    }

    @Override
    public SearchResponseVo search(SearchParam searchParam) {

        //1、根据请求参数构造出真正的DSL语句
        Query query = buildQuery(searchParam);

        //ES远程检索的结果
        SearchHits<Goods> goods = restTemplate.search(query, Goods.class, IndexCoordinates.of("goods"));

        //2、把es的检索结果封装成responseVo
        SearchResponseVo vo = buildResponse(goods,searchParam);

        return vo;
    }

    @Override
    public void updateSkuHotScore(Long skuId, Long hotScore) {
        Optional<Goods> byId = goodsEsRepository.findById(skuId);


        Goods goods = byId.get();
        if (goods!=null){
            goods.setHotScore(hotScore);
        }
        goodsEsRepository.save(goods);
    }

    /**
     * 根据查询结果构建页面ResponseVo
     *
     * @param responseDSL
     * @return
     */
    private SearchResponseVo buildResponse(SearchHits<Goods> responseDSL,SearchParam searchParam) {

        SearchResponseVo responseVo = new SearchResponseVo();
        /**
         *     //品牌 此时vo对象中的id字段保留（不用写） name就是“品牌” value: [{id:100,name:华为,logo:xxx},{id:101,name:小米,log:yyy}]
         *     private List<SearchResponseTmVo> trademarkList;
         *
         *     //所有商品的顶头显示的筛选属性
         *     private List<SearchResponseAttrVo> attrsList = new ArrayList<>();
         *
         *     //检索出来的商品信息
         *     private List<Goods> goodsList = new ArrayList<>();
         *
         */
        List<Goods> goodsList = responseVo.getGoodsList();

        //1、构建相应的商品集合
        List<SearchHit<Goods>> searchHits = responseDSL.getSearchHits();
        for (SearchHit<Goods> hit : searchHits) {
            //命中记录
            Goods content = hit.getContent();
            List<String> title = hit.getHighlightField("title");
            if (!CollectionUtils.isEmpty(title)){
                content.setTitle(title.get(0));
            }
            //获取查询到的商品
            goodsList.add(content);
        }


        //2、构建相应的分页信息
        long totalHits = responseDSL.getTotalHits();
        responseVo.setTotal(totalHits);
        responseVo.setPageNo(searchParam.getPageNo());
        responseVo.setPageSize(searchParam.getPageSize());

        Long totalPages = totalHits%searchParam.getPageSize() == 0 ?(totalHits/searchParam.getPageSize()):(totalHits/searchParam.getPageSize() + 1);
        responseVo.setTotalPages(totalPages);

        //3、构建相应的品牌列表
        List<SearchResponseTmVo> trademarkList = responseVo.getTrademarkList();

        if (responseDSL.hasAggregations()){
            Aggregations aggregations = responseDSL.getAggregations();
            //获取商品的聚合结果
            Terms tmAgg = aggregations.get("tmAgg");

            //所有的品牌
            List<? extends Terms.Bucket> buckets = tmAgg.getBuckets();
            if (!CollectionUtils.isEmpty(buckets)){
                for (Terms.Bucket bucket : buckets) {
                    //遍历一个品牌
                    SearchResponseTmVo tmVo = new SearchResponseTmVo();
                    //设置品牌id
                    tmVo.setTmId(bucket.getKeyAsNumber().longValue());
                    //设置品牌名字
                    Terms tmNameAgg = bucket.getAggregations().get("tmNameAgg");
                    Terms.Bucket nameBucket = tmNameAgg.getBuckets().get(0);
                    tmVo.setTmName(nameBucket.getKeyAsString());
                    //设置品牌路径
                    Terms tmUrlAgg = nameBucket.getAggregations().get("tmUrlAgg");
                    String url = tmUrlAgg.getBuckets().get(0).getKeyAsString();
                    tmVo.setTmLogoUrl(url);

                    trademarkList.add(tmVo);
                }
            }
        }

        //4、构建响应的属性列表【根据聚合分析】
        List<SearchResponseAttrVo> attrsList = responseVo.getAttrsList();
        if (responseDSL.hasAggregations()){
            Nested attrAgg = responseDSL.getAggregations().get("attrAgg");

            Terms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
            if (!CollectionUtils.isEmpty(attrIdAgg.getBuckets())){
                for (Terms.Bucket bucket : attrIdAgg.getBuckets()) {
                    //遍历封装每个属性信息
                    SearchResponseAttrVo attrVo = new SearchResponseAttrVo();
                    //封装属性id
                    attrVo.setAttrId(bucket.getKeyAsNumber().longValue());

                    //封装属性名字
                    Terms attrNameAgg = bucket.getAggregations().get("attrNameAgg");
                    Terms.Bucket attrNameBucket = attrNameAgg.getBuckets().get(0);
                    attrVo.setAttrName(attrNameBucket.getKeyAsString());


                    //封装属性值的可能性
                    List<String> attrValueList = attrVo.getAttrValueList();
                    Terms valueAgg = attrNameBucket.getAggregations().get("attrValueAgg");
                    for (Terms.Bucket valueAggBucket : valueAgg.getBuckets()) {
                        //添加属性值的1种可能
                        attrValueList.add(valueAggBucket.getKeyAsString());
                    }

                    attrsList.add(attrVo);
                }
            }
        }

        return responseVo;
    }

    /**
     * 按照请求参数构建出dsl条件
     *
     * @param searchParam
     * @return
     */
    private Query buildQuery(SearchParam searchParam) {

        //动态判断构建条件
        //遇见复杂对象，优先找  xxxxBuilder
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();

        //1、查询条件
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //模糊检索
        if (!StringUtils.isEmpty(searchParam.getKeyword())) {

            boolQuery.must(QueryBuilders.matchQuery("title", searchParam.getKeyword()));
        }

        //按照三级分类进行过滤查询
        if (searchParam.getCategory3Id() != null) {

            boolQuery.filter(QueryBuilders.termQuery("category3Id", searchParam.getCategory3Id()));
        }

        //按照品牌进行查询
        if (!StringUtils.isEmpty(searchParam.getTrademark())) {

            //trademark=1:小米
            String[] split = searchParam.getTrademark().split(":");
            boolQuery.filter(QueryBuilders.termQuery("tmId", split[0]));
        }

        //按照属性进行检索
        if (searchParam.getProps() != null && searchParam.getProps().length > 0) {

            for (String prop : searchParam.getProps()) {

                String[] split = prop.split(":");
                BoolQueryBuilder query = QueryBuilders.boolQuery();

                query.must(QueryBuilders.termQuery("attrs.attrId", split[0]));
                query.must(QueryBuilders.termQuery("attrs.attrValue", split[1]));

                NestedQueryBuilder attrs = QueryBuilders.nestedQuery("attrs", query, ScoreMode.None);
                boolQuery.filter(attrs);

            }
        }

        builder.withQuery(boolQuery);

        //2、分页条件
        if (searchParam.getPageNo() != null) {
            Integer pageSize = searchParam.getPageSize();
//            PageRequest pageRequest = new PageRequest(searchParam.getPageNo() - 1, pageSize);
            PageRequest of = PageRequest.of(searchParam.getPageNo() - 1, pageSize);
            builder.withPageable(of);
        }

        //3、排序条件
        if (!StringUtils.isEmpty(searchParam.getOrder())) {
            //order=1:desc  1.按照热度排序    2、按照价格排序
            String[] split = searchParam.getOrder().split(":");
            if ("1".equals(split[0])) {
                //1.按照热度排序
                FieldSortBuilder order = SortBuilders.fieldSort("hotScore").order(split[1].equals("asc") ? SortOrder.ASC : SortOrder.DESC);
                builder.withSort(order);
            } else {
                //2、按照价格排序
                FieldSortBuilder order = SortBuilders.fieldSort("price").order(split[1].equals("asc") ? SortOrder.ASC : SortOrder.DESC);
                builder.withSort(order);
            }
        }else {
            //前端没带默认就是热度降序
            FieldSortBuilder order = SortBuilders.fieldSort("hotScore").order(SortOrder.DESC);
            builder.withSort(order);
            searchParam.setOrder("1:desc");
        }

        //4、高亮条件
        if (!StringUtils.isEmpty(searchParam.getKeyword())) {
            HighlightBuilder.Field field = new HighlightBuilder.Field("title");
            field.preTags("<span style='color:red'>");
            field.postTags("</span>");

            builder.withHighlightFields(field);
        }

        //5、聚合条件
        //添加聚合条件，5.1)按照品牌聚合
        TermsAggregationBuilder tmAgg =
                AggregationBuilders.terms("tmAgg").field("tmId").size(100)
                        .subAggregation(
                                AggregationBuilders.terms("tmNameAgg").field("tmName").size(1)
                                .subAggregation(
                                        AggregationBuilders.terms("tmUrlAgg").field("tmLogoUrl").size(1)));
        //5.1.1)tmId的聚合
        //5.1.2)子聚合
        //tmNameAgg
        //5.1.2.1子聚合路径
        builder.addAggregation(tmAgg);


        //5.2)按照属性聚合
        NestedAggregationBuilder nested =
                AggregationBuilders.nested("attrAgg", "attrs")
                        .subAggregation(
                                AggregationBuilders.terms("attrIdAgg").field("attrs.attrId").size(100)
                                        .subAggregation(
                                                AggregationBuilders.terms("attrNameAgg").field("attrs.attrName").size(1)
                                                        .subAggregation(
                                                                AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue").size(100))));

        builder.addAggregation(nested);


        String s = builder.toString();
        System.out.println("DSL语句:" + s);

        return builder.build();
    }
}
